// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/events/gesture_detection/scale_gesture_detector.h"

#include <limits.h>

#include <algorithm>
#include <cmath>

#include "base/logging.h"
#include "ui/events/gesture_detection/motion_event.h"
#include "ui/events/gesture_detection/scale_gesture_listeners.h"

using base::TimeDelta;
using base::TimeTicks;

namespace ui {
namespace {

    // Using a small epsilon when comparing slop distances allows pixel perfect
    // slop determination when using fractional DPI coordinates (assuming the slop
    // region and DPI scale are reasonably proportioned).
    const float kSlopEpsilon = .05f;

    const int kTouchStabilizeTimeMs = 128;

    const float kScaleFactor = .5f;

} // namespace

// Note: These constants were taken directly from the default (unscaled)
// versions found in Android's ViewConfiguration. Do not change these default
// values without explicitly consulting an OWNER.
ScaleGestureDetector::Config::Config()
    : span_slop(16)
    , min_scaling_touch_major(48)
    , min_scaling_span(200)
    , min_pinch_update_span_delta(0)
    , stylus_scale_enabled(false)
{
}

ScaleGestureDetector::Config::~Config() { }

ScaleGestureDetector::ScaleGestureDetector(const Config& config,
    ScaleGestureListener* listener)
    : listener_(listener)
    , stylus_scale_enabled_(config.stylus_scale_enabled)
    , focus_x_(0)
    , focus_y_(0)
    , curr_span_(0)
    , prev_span_(0)
    , initial_span_(0)
    , curr_span_x_(0)
    , curr_span_y_(0)
    , prev_span_x_(0)
    , prev_span_y_(0)
    , in_progress_(0)
    , span_slop_(0)
    , min_span_(0)
    , touch_upper_(0)
    , touch_lower_(0)
    , touch_history_last_accepted_(0)
    , touch_history_direction_(0)
    , touch_min_major_(0)
    , touch_max_major_(0)
    , anchored_scale_start_x_(0)
    , anchored_scale_start_y_(0)
    , anchored_scale_mode_(ANCHORED_SCALE_MODE_NONE)
    , event_before_or_above_starting_gesture_event_(false)
{
    DCHECK(listener_);
    span_slop_ = config.span_slop + kSlopEpsilon;
    touch_min_major_ = config.min_scaling_touch_major;
    touch_max_major_ = std::min(config.min_scaling_span / std::sqrt(2.f),
        2.f * touch_min_major_);
    min_span_ = config.min_scaling_span + kSlopEpsilon;
    ResetTouchHistory();
}

ScaleGestureDetector::~ScaleGestureDetector() { }

bool ScaleGestureDetector::OnTouchEvent(const MotionEvent& event)
{
    curr_time_ = event.GetEventTime();

    const int action = event.GetAction();

    const int count = static_cast<int>(event.GetPointerCount());
    const bool is_stylus_button_down = (event.GetButtonState() & MotionEvent::BUTTON_STYLUS_PRIMARY) != 0;

    const bool anchored_scale_cancelled = anchored_scale_mode_ == ANCHORED_SCALE_MODE_STYLUS && !is_stylus_button_down;

    const bool stream_complete = action == MotionEvent::ACTION_UP || action == MotionEvent::ACTION_CANCEL || anchored_scale_cancelled || (action == MotionEvent::ACTION_POINTER_DOWN && InAnchoredScaleMode());

    if (action == MotionEvent::ACTION_DOWN || stream_complete) {
        // Reset any scale in progress with the listener.
        // If it's an ACTION_DOWN we're beginning a new event stream.
        // This means the app probably didn't give us all the events. Shame on it.
        if (in_progress_) {
            listener_->OnScaleEnd(*this, event);
            ResetScaleWithSpan(0);
        } else if (InAnchoredScaleMode() && stream_complete) {
            ResetScaleWithSpan(0);
        }

        if (stream_complete) {
            ResetTouchHistory();
            return true;
        }
    }

    if (!in_progress_ && stylus_scale_enabled_ && !InAnchoredScaleMode() && !stream_complete && is_stylus_button_down) {
        // Start of a stylus scale gesture.
        anchored_scale_start_x_ = event.GetX();
        anchored_scale_start_y_ = event.GetY();
        anchored_scale_mode_ = ANCHORED_SCALE_MODE_STYLUS;
        initial_span_ = 0;
    }

    const bool config_changed = action == MotionEvent::ACTION_DOWN || action == MotionEvent::ACTION_POINTER_UP || action == MotionEvent::ACTION_POINTER_DOWN || anchored_scale_cancelled;

    const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP;
    const int skip_index = pointer_up ? event.GetActionIndex() : -1;

    // Determine focal point.
    float sum_x = 0, sum_y = 0;
    const int unreleased_point_count = pointer_up ? count - 1 : count;
    const float inverse_unreleased_point_count = 1.0f / unreleased_point_count;

    float focus_x;
    float focus_y;
    if (InAnchoredScaleMode()) {
        // In double tap mode, the focal pt is always where the double tap
        // gesture started.
        focus_x = anchored_scale_start_x_;
        focus_y = anchored_scale_start_y_;
        if (event.GetY() < focus_y) {
            event_before_or_above_starting_gesture_event_ = true;
        } else {
            event_before_or_above_starting_gesture_event_ = false;
        }
    } else {
        for (int i = 0; i < count; i++) {
            if (skip_index == i)
                continue;
            sum_x += event.GetX(i);
            sum_y += event.GetY(i);
        }

        focus_x = sum_x * inverse_unreleased_point_count;
        focus_y = sum_y * inverse_unreleased_point_count;
    }

    AddTouchHistory(event);

    // Determine average deviation from focal point.
    float dev_sum_x = 0, dev_sum_y = 0;
    for (int i = 0; i < count; i++) {
        if (skip_index == i)
            continue;

        dev_sum_x += std::abs(event.GetX(i) - focus_x);
        dev_sum_y += std::abs(event.GetY(i) - focus_y);
    }
    // Convert the resulting diameter into a radius, to include touch
    // radius in overall deviation.
    const float touch_radius = touch_history_last_accepted_ / 2;

    const float dev_x = dev_sum_x * inverse_unreleased_point_count + touch_radius;
    const float dev_y = dev_sum_y * inverse_unreleased_point_count + touch_radius;

    // Span is the average distance between touch points through the focal point;
    // i.e. the diameter of the circle with a radius of the average deviation from
    // the focal point.
    const float span_x = dev_x * 2;
    const float span_y = dev_y * 2;
    float span;
    if (InAnchoredScaleMode()) {
        span = span_y;
    } else {
        span = std::sqrt(span_x * span_x + span_y * span_y);
    }

    // Dispatch begin/end events as needed.
    // If the configuration changes, notify the app to reset its current state by
    // beginning a fresh scale event stream.
    const bool was_in_progress = in_progress_;
    focus_x_ = focus_x;
    focus_y_ = focus_y;
    if (!InAnchoredScaleMode() && in_progress_ && (span < min_span_ || config_changed)) {
        listener_->OnScaleEnd(*this, event);
        ResetScaleWithSpan(span);
    }
    if (config_changed) {
        prev_span_x_ = curr_span_x_ = span_x;
        prev_span_y_ = curr_span_y_ = span_y;
        initial_span_ = prev_span_ = curr_span_ = span;
    }

    const float min_span = InAnchoredScaleMode() ? span_slop_ : min_span_;
    if (!in_progress_ && span >= min_span && (was_in_progress || std::abs(span - initial_span_) > span_slop_)) {
        prev_span_x_ = curr_span_x_ = span_x;
        prev_span_y_ = curr_span_y_ = span_y;
        prev_span_ = curr_span_ = span;
        prev_time_ = curr_time_;
        in_progress_ = listener_->OnScaleBegin(*this, event);
    }

    // Handle motion; focal point and span/scale factor are changing.
    if (action == MotionEvent::ACTION_MOVE) {
        curr_span_x_ = span_x;
        curr_span_y_ = span_y;
        curr_span_ = span;

        bool update_prev = true;

        if (in_progress_) {
            update_prev = listener_->OnScale(*this, event);
        }

        if (update_prev) {
            prev_span_x_ = curr_span_x_;
            prev_span_y_ = curr_span_y_;
            prev_span_ = curr_span_;
            prev_time_ = curr_time_;
        }
    }

    return true;
}

bool ScaleGestureDetector::IsInProgress() const { return in_progress_; }

bool ScaleGestureDetector::InAnchoredScaleMode() const
{
    return anchored_scale_mode_ != ANCHORED_SCALE_MODE_NONE;
}

float ScaleGestureDetector::GetFocusX() const { return focus_x_; }

float ScaleGestureDetector::GetFocusY() const { return focus_y_; }

float ScaleGestureDetector::GetCurrentSpan() const { return curr_span_; }

float ScaleGestureDetector::GetCurrentSpanX() const { return curr_span_x_; }

float ScaleGestureDetector::GetCurrentSpanY() const { return curr_span_y_; }

float ScaleGestureDetector::GetPreviousSpan() const { return prev_span_; }

float ScaleGestureDetector::GetPreviousSpanX() const { return prev_span_x_; }

float ScaleGestureDetector::GetPreviousSpanY() const { return prev_span_y_; }

float ScaleGestureDetector::GetScaleFactor() const
{
    if (InAnchoredScaleMode()) {
        // Drag is moving up; the further away from the gesture start, the smaller
        // the span should be, the closer, the larger the span, and therefore the
        // larger the scale.
        const bool scale_up = (event_before_or_above_starting_gesture_event_ && (curr_span_ < prev_span_)) || (!event_before_or_above_starting_gesture_event_ && (curr_span_ > prev_span_));
        const float span_diff = (std::abs(1.f - (curr_span_ / prev_span_)) * kScaleFactor);
        return prev_span_ <= 0 ? 1.f
                               : (scale_up ? (1.f + span_diff) : (1.f - span_diff));
    }
    return prev_span_ > 0 ? curr_span_ / prev_span_ : 1;
}

base::TimeDelta ScaleGestureDetector::GetTimeDelta() const
{
    return curr_time_ - prev_time_;
}

base::TimeTicks ScaleGestureDetector::GetEventTime() const
{
    return curr_time_;
}

bool ScaleGestureDetector::OnDoubleTap(const MotionEvent& ev)
{
    // Double tap: start watching for a swipe.
    anchored_scale_start_x_ = ev.GetX();
    anchored_scale_start_y_ = ev.GetY();
    anchored_scale_mode_ = ANCHORED_SCALE_MODE_DOUBLE_TAP;
    return true;
}

void ScaleGestureDetector::AddTouchHistory(const MotionEvent& ev)
{
    const base::TimeTicks current_time = ev.GetEventTime();
    DCHECK(!current_time.is_null());
    const int count = static_cast<int>(ev.GetPointerCount());
    bool accept = touch_history_last_accepted_time_.is_null() || (current_time - touch_history_last_accepted_time_) >= base::TimeDelta::FromMilliseconds(kTouchStabilizeTimeMs);
    float total = 0;
    int sample_count = 0;
    for (int i = 0; i < count; i++) {
        const bool has_last_accepted = !std::isnan(touch_history_last_accepted_);
        const int history_size = static_cast<int>(ev.GetHistorySize());
        const int pointersample_count = history_size + 1;
        for (int h = 0; h < pointersample_count; h++) {
            float major;
            if (h < history_size) {
                major = ev.GetHistoricalTouchMajor(i, h);
            } else {
                major = ev.GetTouchMajor(i);
            }
            if (major < touch_min_major_)
                major = touch_min_major_;
            if (major > touch_max_major_)
                major = touch_max_major_;
            total += major;

            if (std::isnan(touch_upper_) || major > touch_upper_) {
                touch_upper_ = major;
            }
            if (std::isnan(touch_lower_) || major < touch_lower_) {
                touch_lower_ = major;
            }

            if (has_last_accepted) {
                const float major_delta = major - touch_history_last_accepted_;
                const int direction_sig = major_delta > 0 ? 1 : (major_delta < 0 ? -1 : 0);
                if (direction_sig != touch_history_direction_ || (direction_sig == 0 && touch_history_direction_ == 0)) {
                    touch_history_direction_ = direction_sig;
                    touch_history_last_accepted_time_ = h < history_size
                        ? ev.GetHistoricalEventTime(h)
                        : ev.GetEventTime();
                    accept = false;
                }
            }
        }
        sample_count += pointersample_count;
    }

    const float avg = total / sample_count;

    if (accept) {
        float new_accepted = (touch_upper_ + touch_lower_ + avg) / 3;
        touch_upper_ = (touch_upper_ + new_accepted) / 2;
        touch_lower_ = (touch_lower_ + new_accepted) / 2;
        touch_history_last_accepted_ = new_accepted;
        touch_history_direction_ = 0;
        touch_history_last_accepted_time_ = ev.GetEventTime();
    }
}

void ScaleGestureDetector::ResetTouchHistory()
{
    touch_upper_ = std::numeric_limits<float>::quiet_NaN();
    touch_lower_ = std::numeric_limits<float>::quiet_NaN();
    touch_history_last_accepted_ = std::numeric_limits<float>::quiet_NaN();
    touch_history_direction_ = 0;
    touch_history_last_accepted_time_ = base::TimeTicks();
}

void ScaleGestureDetector::ResetScaleWithSpan(float span)
{
    in_progress_ = false;
    initial_span_ = span;
    anchored_scale_mode_ = ANCHORED_SCALE_MODE_NONE;
}

} // namespace ui
